Skip to content

Release 5.5.2: aggregation pipeline encoding and with_session scoping fixes#19

Merged
AdrianCurtin merged 1 commit into
mainfrom
v552
Jun 11, 2026
Merged

Release 5.5.2: aggregation pipeline encoding and with_session scoping fixes#19
AdrianCurtin merged 1 commit into
mainfrom
v552

Conversation

@AdrianCurtin

@AdrianCurtin AdrianCurtin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Release 5.5.2. Two aggregation fixes plus scoping/security hardening for aggregations run inside Parse.with_session blocks.

Large aggregation pipelines no longer fail with "Invalid aggregate stage '0'"

  • FIXED: An aggregation whose request URL exceeds ~2KB (a group_by, group_by_date, distinct, or custom aggregate pipeline with a large $in / $match) is rewritten from a GET to a POST carrying _method=GET, moving the query into the request body. The pipeline was sent in the body as a URL-encoded string, but Parse Server's aggregate endpoint only JSON-decodes query-string params, not body params — so the pipeline arrived as a raw string and was rejected with Invalid aggregate stage '0', causing the aggregation to return an empty result. The long-URL override now sends a JSON body for the aggregate endpoint so the pipeline is delivered as a real array (boolean params such as rawValues are preserved as booleans). The historical URL-encoded override is unchanged for find and other endpoints, which Parse Server already decodes correctly.

Aggregations inside Parse.with_session blocks are now scoped

  • FIXED: group_by_date, group_by, distinct, and count (aggregation branch) now detect the ambient session token set by Parse.with_session and treat the query as scoped — consistent with how Parse::Client#request already scopes REST find/get/count calls in the same block. Previously query_is_scoped? / distinct_query_is_scoped? consulted only the query instance's own session_token= / scope_to_user / scope_to_role and ignored Parse.current_session_token, so an aggregation inside a with_session block ran unscoped as the master key and returned all rows regardless of ACL. The checks now include the ambient: when scoped and mongo-direct is available the aggregation auto-promotes (ACL/CLP enforced); when scoped and mongo-direct is unavailable it fails closed with MongoDirectRequired rather than silently leaking rows.
  • FIXED: group_by_date now also fails closed (MongoDirectRequired) when the query is scoped but mongo-direct is unavailable — matching the existing behavior of group_by, distinct, and count. Previously group_by_date silently fell back to the REST /aggregate endpoint in that case.
  • FIXED: A regression introduced in 5.5.1 where group_by_date, group_by, and pipeline-based aggregations called inside a Parse.with_session block returned empty results {}. The ambient session token was forwarded as an HTTP session-token header (suppressing the master key), causing Parse Server's REST /aggregate endpoint — which is master-key-only — to return a 401/403. The REST aggregate call sites now force use_master_key: true so the ambient cannot suppress it, unless the caller explicitly set use_master_key: false.

Tests

  • New body_builder_method_override_test.rb covering the long-URL GET→POST override: JSON body for the aggregate endpoint (pipeline preserved as an array, booleans preserved), unchanged URL-encoded body for find, short URLs left as GET, and the /aggregate/ path guard.
  • Expanded aggregation_auto_promotion_test.rb with ambient-session promote / fail-closed coverage for group_by, group_by_date, distinct, count, Query#aggregate, and GroupBy#raw, plus explicit use_master_key: false handling.

Copilot AI review requested due to automatic review settings June 11, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Ruby Parse Server SDK to treat fiber-local ambient sessions (Parse.with_session) as an authorization scope for aggregation-style query terminals, ensuring scoped aggregations auto-route to mongo-direct (or fail closed) instead of silently returning unscoped/master-key results. It also bumps the gem version to 5.5.2 and documents the behavior in the changelog.

Changes:

  • Treat Parse.with_session’s ambient session token as scope in aggregation scoping checks (distinct_query_is_scoped?, query_is_scoped?), so aggregations promote to mongo-direct or raise MongoDirectRequired when direct Mongo is unavailable.
  • Force REST /aggregate calls to use use_master_key: true unless the caller explicitly set use_master_key: false, preventing ambient sessions from suppressing the master key.
  • Add tests for ambient-session aggregation behavior and for respecting explicit use_master_key: false, plus changelog/version updates.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

File Description
lib/parse/query.rb Implements ambient-session scoping for aggregations, fail-closed behavior when mongo-direct is unavailable, REST aggregate master-key forcing, and adds a REST failure warning.
test/lib/parse/aggregation_auto_promotion_test.rb Adds regression/behavior tests for ambient Parse.with_session scoping and explicit use_master_key: false on REST aggregation paths.
lib/parse/stack/version.rb Bumps version to 5.5.2.
CHANGELOG.md Documents the 5.5.2 changes around scoped aggregations in Parse.with_session and the REST aggregate master-key behavior.
Comments suppressed due to low confidence (3)

lib/parse/query.rb:6565

  • This raises MongoDirectRequired for scoped aggregations, but the exception message (raised by raise_scoped_aggregation_requires_mongo_direct!) only mentions session_token / scope_to_user / scope_to_role. Since ambient Parse.with_session is now considered scope, the error text should mention the ambient session too so callers understand why they’re being blocked.
      # Format the group field name
      formatted_group_field = @query.send(:format_aggregation_field, @group_field)

      # Auto-promote scoped queries to mongo-direct so the SDK's three-layer

lib/parse/query.rb:7259

  • Avoid referencing approximate line numbers in comments ("line ~3575") since they drift over time and become misleading. Prefer pointing to the method / behavior being mirrored instead.
    private

    # Execute a date-based group aggregation operation.

lib/parse/query.rb:6560

  • The scoped-query explanation just above this block is now incomplete: query_is_scoped? includes the ambient session from Parse.with_session, but the comment still lists only session_token / acl_user / acl_role. Update it so future maintainers don’t miss that with_session also triggers mongo-direct promotion / fail-closed behavior.
      # alternatives are "$size on a non-array" (server error) and
      # lexicographic array compare (silently wrong), neither of which is
      # what the caller meant.
      validate_sort_target_for_operation!(operation)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@AdrianCurtin AdrianCurtin requested a review from Copilot June 11, 2026 14:39
@AdrianCurtin AdrianCurtin changed the title Honor ambient Parse.with_session for aggregations Release 5.5.2: aggregation pipeline encoding and with_session scoping fixes Jun 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.

Comment on lines +303 to +308
env[:request_headers][HTTP_METHOD_OVERRIDE] = "GET"
env[:request_headers][CONTENT_TYPE] = "application/x-www-form-urlencoded"
# parse-sever looks for method overrides in the body under the `_method` param.
# so we will add it to the query string, which will now go into the body.
env[:body] = "_method=GET&" + env[:url].query
env[:url].query = nil
Comment thread lib/parse/query.rb Outdated
Comment on lines +6008 to +6016
rest_opts = @query.send(:_opts)
rest_opts[:use_master_key] = true unless rest_opts[:use_master_key] == false
@cached_response = @query.client.aggregate_pipeline(
@query.instance_variable_get(:@table),
@pipeline,
headers: {},
raw_values: @raw_values,
raw_field_names: @raw_field_names,
**@query.send(:_opts),
**rest_opts,
Comment thread lib/parse/query.rb
Comment on lines +7365 to +7368
unless response.success?
warn "[Parse::GroupByDate] aggregate failed (#{@query.instance_variable_get(:@table)}" \
" :#{@date_field} :#{@interval}): #{response.error.inspect}"
end
Comment on lines +265 to +273
Parse::MongoDB.define_singleton_method(:aggregate) do |_class_name, _pipeline, **_kw|
direct_called = true
[]
end
Parse.with_session("r:ambient-tok") { @query.count }
assert direct_called, "expected scoped #count (ambient session) to route through mongo-direct"
ensure
Parse::MongoDB.singleton_class.remove_method(:aggregate) if Parse::MongoDB.singleton_class.method_defined?(:aggregate)
end
Comment thread lib/parse/client/body_builder.rb Outdated
else
env[:request_headers][HTTP_METHOD_OVERRIDE] = "GET"
env[:request_headers][CONTENT_TYPE] = "application/x-www-form-urlencoded"
# parse-sever looks for method overrides in the body under the `_method` param.
Comment on lines +362 to +364
def aggregate_request?(url)
url.path.to_s.include?("/aggregate/")
end

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Comment thread lib/parse/query.rb Outdated
Comment on lines +6413 to +6419
if query_is_scoped?
if parse_mongodb_available?
return execute_group_aggregation_direct(operation, aggregation_expr, formatted_group_field)
else
@query.send(:raise_scoped_aggregation_requires_mongo_direct!)
end
end
Comment thread lib/parse/query.rb Outdated
Comment on lines +1824 to +1825
ambient = ambient_session_token
return true if ambient.is_a?(String) && !ambient.empty?
Comment thread lib/parse/query.rb Outdated
Comment on lines +6829 to +6830
ambient = @query.send(:ambient_session_token)
return true if ambient.is_a?(String) && !ambient.empty?
Comment thread lib/parse/query.rb Outdated
Comment on lines +7572 to +7573
ambient = @query.send(:ambient_session_token)
return true if ambient.is_a?(String) && !ambient.empty?
Comment on lines +1955 to +1956
encoded_len = { pipeline: long_pipeline.to_json }.to_a
.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&").length
Send JSON bodies for long-URL aggregate overrides and scope aggregations to ambient sessions. BodyBuilder now detects /aggregate/ requests and builds a JSON POST body (preserving pipeline as an Array and boolean params) to avoid Parse Server rejecting urlencoded pipeline strings. Query/aggregation logic was updated to consult the ambient Parse.with_session session token (in distinct_query_is_scoped? and query_is_scoped?), to auto-promote scoped aggregations to mongo-direct when available and to raise MongoDirectRequired when mongo-direct is unavailable. REST /aggregate calls force use_master_key: true unless explicitly set false to avoid ambient sessions suppressing the master key and causing 401/403s. Tests for ambient session scoping and the long-URL aggregate override were added/updated, CHANGELOG updated, and the stack version bumped to 5.5.2.
@AdrianCurtin AdrianCurtin merged commit e32211a into main Jun 11, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants